﻿using Microsoft.Data.SqlClient;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Avin.Infrastructure.Entity
{
    public static class EntityInitializer
    {
        /// <summary>
        /// 获取属性过滤
        /// </summary>
        private const BindingFlags BindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        /// <summary>
        /// 将类型与该类型所有的可写且未被忽略属性之间建立映射
        /// </summary>
        private static readonly ConcurrentDictionary<Type, Dictionary<string, PropertyMoreInfo>> PropertyMappings = new();

        /// <summary>
        /// 生成指定类型中属性与数据库字段的对应关系映射
        /// </summary>
        /// <param name="type"></param>
        public static Dictionary<string, PropertyMoreInfo> GenerateTypePropertyMapping(Type type)
        {
            if (type == null)
                return null;
            if (PropertyMappings.ContainsKey(type))
                return PropertyMappings[type];
            var properties = type.GetProperties(BindingFlag);
            var propertyColumnMapping = new Dictionary<string, PropertyMoreInfo>();
            foreach (var p in properties)
            {
                //如果该属性是可读并且未被忽略的，则有可能在实例化该属性对应的类时用得上
                if (!p.CanWrite)
                    continue;

                var propertyMoreInfo = new PropertyMoreInfo
                {
                    PropertyInfo = p,
                    ValidateAttribute = null,
                    EntityFieldAttribute = null
                };
                var attributes = Attribute.GetCustomAttributes(p);
                foreach (var attribute in attributes)
                {
                    //检查是否CustomAttribute或者继承CustomAttribute
                    if (attribute.GetType().IsSubclassOf(typeof(ValidateAttribute)))
                    {
                        propertyMoreInfo.ValidateAttribute = (ValidateAttribute)attribute;
                    }
                    //检查是否UpdateAttribute
                    else if (attribute.GetType() == typeof(EntityFieldAttribute))
                    {
                        propertyMoreInfo.EntityFieldAttribute = (EntityFieldAttribute)attribute;
                    }
                }
                if (propertyColumnMapping.ContainsKey(p.Name.ToLower()))
                    propertyColumnMapping[p.Name.ToLower()] = propertyMoreInfo;
                else
                    propertyColumnMapping.Add(p.Name.ToLower(), propertyMoreInfo);
            }
            if (!IsAnonymousType(type))
                PropertyMappings.AddOrUpdate(type, propertyColumnMapping, (a, b) => b);
            return propertyColumnMapping;
        }

        /// <summary>
        /// 根据CustomAttribute验证Entity
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        /// <param name="ignoreFields">忽略验证的字段</param>
        /// <returns></returns>
        public static Dictionary<string, string> Validate<T>(T entity, params string[] ignoreFields)
        {
            return Validate(typeof(T), entity, ignoreFields);
        }

        /// <summary>
        /// 根据CustomAttribute验证Entity
        /// </summary>
        /// <param name="type"></param>
        /// <param name="value"></param>
        /// <param name="ignoreFields"></param>
        /// <returns></returns>
        public static Dictionary<string, string> Validate(Type type, object value, params string[] ignoreFields)
        {
            var dict = new Dictionary<string, string>();
            var propertyColumnMapping = GenerateTypePropertyMapping(type);

            if (ignoreFields == null)
            {
                ignoreFields = Array.Empty<string>();
            }
            else
            {
                for (int i = 0; i < ignoreFields.Length; i++)
                {
                    ignoreFields[i] = ignoreFields[i].ToLower();
                }
            }

            foreach (var pa in propertyColumnMapping)
            {
                if (pa.Value.ValidateAttribute == null || ignoreFields.Contains(pa.Key))
                    continue;

                if (string.IsNullOrWhiteSpace(pa.Value.ValidateAttribute.DisplayName))
                {
                    pa.Value.ValidateAttribute.DisplayName = string.IsNullOrWhiteSpace(pa.Value.ValidateAttribute.ColumnName) ? pa.Key : pa.Value.ValidateAttribute.ColumnName;
                }
                var obj = pa.Value.PropertyInfo.GetValue(value, null);
                var strError = pa.Value.ValidateAttribute.Validate(ref obj);
                if (string.IsNullOrWhiteSpace(strError))
                {
                    if (pa.Value.ValidateAttribute.IsUseDefault)
                        pa.Value.PropertyInfo.SetValue(value, obj);
                    else if (pa.Value.ValidateAttribute is StringValidateAttribute)
                        pa.Value.PropertyInfo.SetValue(value, obj);
                }
                else
                {
                    dict.Add(pa.Key, strError);
                }
            }
            return dict;
        }

        public static List<T> DataTableToList<T>(DataTable table, Action<T> onItemCompleted = null)
        {
            if (table == null) return null;
            //如果T的类型满足以下条件：字符串、ValueType或者是Nullable<ValueType>
            if (typeof(T) == typeof(string) || typeof(T) == typeof(byte[]) || typeof(T).IsValueType)
            {
                return GetSimpleEntity<T>(table);
            }
            return GetComplexEntity<T>(table, onItemCompleted);
        }

        private static List<T> GetSimpleEntity<T>(DataTable table)
        {
            var array = new List<T>();
            for (var i = 0; i < table.Rows.Count; i++)
            {
                array.Add((T)GetValue(typeof(T), table.Rows[i][0]));
            }
            return array;
        }

        private static List<T> GetComplexEntity<T>(DataTable table, Action<T> onItemCompleted = null)
        {
            var type = typeof(T);
            if (IsAnonymousType(type))
            {
                return GetAnonymousEntity<T>(table);
            }
            else
            {
                var propertyAndAttributes = GenerateTypePropertyMapping(type); ;
                var array = new List<T>();
                var columns = table.Columns;
                for (var i = 0; i < table.Rows.Count; i++)
                {
                    var entity = (T)Activator.CreateInstance(type);
                    foreach (var pa in propertyAndAttributes)
                    {
                        if (columns.Contains(pa.Key) && DBNull.Value != table.Rows[i][pa.Key])
                        {
                            pa.Value.PropertyInfo.SetValue(entity, GetValue(pa.Value.PropertyInfo.PropertyType, table.Rows[i][pa.Key], (pa.Value.ValidateAttribute == null ? "" : pa.Value.ValidateAttribute.Format)), null);
                        }
                    }
                    if (onItemCompleted != null) onItemCompleted.Invoke(entity);
                    array.Add(entity);
                }
                return array;
            }
        }

        private static List<T> GetAnonymousEntity<T>(DataTable table)
        {
            List<T> array = new();
            var properties = typeof(T).GetProperties();
            var columns = table.Columns;
            for (var i = 0; i < table.Rows.Count; i++)
            {
                object[] datas = new object[properties.Length];
                for (int j = 0; j < properties.Length; j++)
                {
                    if (columns.Contains(properties[j].Name) && DBNull.Value != table.Rows[i][properties[j].Name])
                    {
                        datas[j] = table.Rows[i][properties[j].Name];
                    }
                }
                array.Add((T)typeof(T).GetConstructor(typeof(T).GetGenericArguments()).Invoke(datas));
            }
            return array;
        }

        public static List<T> DataReaderToList<T>(DbDataReader reader, Action<T> onItemCompleted = null) where T : new()
        {
            if (reader == null) return null;
            //如果T的类型满足以下条件：字符串、ValueType或者是Nullable<ValueType>
            if (typeof(T) == typeof(string) || typeof(T) == typeof(byte[]) || typeof(T).IsValueType)
            {
                return GetSimpleEntity<T>(reader);
            }
            return GetComplexEntity<T>(reader, onItemCompleted);
        }

        private static List<T> GetSimpleEntity<T>(IDataReader reader)
        {
            var array = new List<T>();
            while (reader.Read())
            {
                array.Add((T)GetValue(typeof(T), reader[0]));
            }
            return array;
        }

        private static List<T> GetComplexEntity<T>(IDataReader reader, Action<T> onItemCompleted = null) where T : new()
        {
            var type = typeof(T);
            if (IsAnonymousType(type))
            {
                return GetAnonymousEntity<T>(reader);
            }
            else
            {
                var propertyAndAttributes = GenerateTypePropertyMapping(type);
                var array = new List<T>();
                while (reader.Read())
                {
                    var entity = new T();
                    foreach (var pa in propertyAndAttributes.Where(p => null != reader[p.Key] && DBNull.Value != reader[p.Key]))
                    {
                        pa.Value.PropertyInfo.SetValue(entity, GetValue(pa.Value.PropertyInfo.PropertyType, reader[pa.Key], (pa.Value.ValidateAttribute == null ? "" : pa.Value.ValidateAttribute.Format)), null);
                    }
                    if (onItemCompleted != null) onItemCompleted.Invoke(entity);
                    array.Add(entity);
                }
                return array;
            }
        }

        private static List<T> GetAnonymousEntity<T>(IDataReader reader)
        {
            List<T> array = new();
            var properties = typeof(T).GetProperties();
            while (reader.Read())
            {
                object[] datas = new object[properties.Length];
                for (int j = 0; j < properties.Length; j++)
                {
                    if (DBNull.Value != reader[properties[j].Name])
                    {
                        datas[j] = reader[properties[j].Name];
                    }
                }
                array.Add((T)typeof(T).GetConstructor(typeof(T).GetGenericArguments()).Invoke(datas));
            }
            return array;
        }

        private static object GetValue(Type type, object value, string format = "")
        {
            var dataType = type;
            var isGenericType = type.IsGenericType;
            if (isGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { dataType = type.GetGenericArguments()[0]; }
            if (dataType.IsValueType)
            {
                string strTemp;
                if (dataType == typeof(int))
                {
                    if (value == null) { return isGenericType ? null : (object)0; }
                    strTemp = value.ToString();
                    if (int.TryParse(strTemp, out int newValue)) { return newValue; } else { return isGenericType ? null : (object)0; }
                }

                if (dataType == typeof(DateTime))
                {
                    if (value == null) { return isGenericType ? null : (object)DateTime.MinValue; }
                    strTemp = value.ToString();
                    if (DateTime.TryParse(strTemp, out DateTime newValue)) { return newValue; } else { return isGenericType ? null : (object)DateTime.MinValue; }
                }

                if (dataType == typeof(float))
                {
                    if (value == null) { return isGenericType ? null : (object)0; }
                    strTemp = value.ToString();
                    if (float.TryParse(strTemp, out float newValue)) { return newValue; } else { return isGenericType ? null : (object)0; }
                }

                if (dataType == typeof(double))
                {
                    if (value == null) { return isGenericType ? null : (object)0; }
                    strTemp = value.ToString();
                    if (double.TryParse(strTemp, out double newValue)) { return newValue; } else { return isGenericType ? null : (object)0; }
                }

                if (dataType == typeof(bool))
                {
                    if (value == null) { return isGenericType ? null : (object)true; }
                    strTemp = value.ToString();
                    if (bool.TryParse(strTemp, out bool newValue)) { return newValue; } else { return isGenericType ? null : (object)true; }
                }

                if (dataType == typeof(Guid))
                {
                    if (value == null) { return isGenericType ? null : (object)Guid.Empty; }
                    strTemp = value.ToString();
                    if (Guid.TryParse(strTemp, out Guid newValue)) { return newValue; } else { return isGenericType ? null : (object)Guid.Empty; }
                }
            }
            else
            {
                if (dataType == typeof(string))
                {
                    if (string.IsNullOrWhiteSpace(format))
                    {
                        format = "{0}";
                    }
                    else
                    {
                        format = "{0:" + format + "}";
                    }
                    return value == null ? null : string.Format(format, value);
                }
            }
            return value;
        }

        /// <summary>
        /// 获取表名
        /// </summary>
        /// <param name="dbProviderFactory"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string GetTableName(DbProviderFactory dbProviderFactory, Type type)
        {
            if (dbProviderFactory == null)
                return type.Name;

            if (dbProviderFactory is SqlClientFactory)
                return "[" + type.Name + "]";
            else if (dbProviderFactory is MySqlClientFactory)
                return "`" + type.Name + "`";

            return type.Name;
        }

        /// <summary>
        /// 获取字段名
        /// </summary>
        /// <param name="dbProviderFactory"></param>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string GetColumnName(DbProviderFactory dbProviderFactory, Type type, string name)
        {
            if (dbProviderFactory == null)
                return name;

            if (dbProviderFactory is SqlClientFactory)
                return "[" + name + "]";

            if (dbProviderFactory is MySqlClientFactory)
                return "`" + name + "`";

            return name;
        }

        /// <summary>
        /// 是否匿名类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsAnonymousType(Type type)
        {
            if (!type.IsGenericType)
                return false;

            if ((type.Attributes & TypeAttributes.NotPublic) != TypeAttributes.NotPublic)
                return false;

            if (!Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false))
                return false;

            return type.Name.Contains("AnonymousType");
        }

        public class PropertyMoreInfo
        {
            public PropertyInfo PropertyInfo { get; set; }

            public ValidateAttribute ValidateAttribute { get; set; }

            public EntityFieldAttribute EntityFieldAttribute { get; set; }
        }
    }

    public static class SqlExtension
    {
        /// <summary>
        /// DataSet转换成List
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ds"></param>
        /// <param name="tableIndex"></param>
        /// <returns></returns>
        public static List<T> ToList<T>(this DataSet ds, int tableIndex = 0, Action<T> onItemCompleted = null)
        {
            if (ds == null || (ds.Tables.Count - 1) < tableIndex || tableIndex < 0)
                return null;

            return EntityInitializer.DataTableToList<T>(ds.Tables[tableIndex], onItemCompleted);
        }

        /// <summary>
        /// DataSet转换成T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ds"></param>
        /// <param name="tableIndex"></param>
        /// <returns></returns>
        public static T ToSingle<T>(this DataSet ds, int tableIndex = 0)
        {
            if (ds == null || (ds.Tables.Count - 1) < tableIndex || tableIndex < 0)
                return default;

            var array = EntityInitializer.DataTableToList<T>(ds.Tables[tableIndex]);
            if (array == null || array.Count == 0)
                return default;

            return array[0];
        }

        /// <summary>
        /// DataTable转换成List
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="table"></param>
        /// <returns></returns>
        public static List<T> ToList<T>(this DataTable table) where T : new()
        {
            return table == null ? null : EntityInitializer.DataTableToList<T>(table);
        }

        /// <summary>
        /// DataTable转换成T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="table"></param>
        /// <returns></returns>
        public static T ToSingle<T>(this DataTable table) where T : new()
        {
            if (table == null) return default;
            var array = EntityInitializer.DataTableToList<T>(table);
            if (array == null || array.Count == 0) return default;
            return array[0];
        }

        /// <summary>
        /// DataTable转换成List
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static List<T> ToList<T>(this DbDataReader reader) where T : new()
        {
            return reader == null ? null : EntityInitializer.DataReaderToList<T>(reader);
        }

        /// <summary>
        /// DataReader转换成T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static T ToSingle<T>(this DbDataReader reader) where T : new()
        {
            if (reader == null) return default;
            var array = EntityInitializer.DataReaderToList<T>(reader);
            if (array == null || array.Count == 0) return default;
            return array[0];
        }
    }
}
